package com.javatest.classfile;

import org.junit.Test;
import org.objectweb.asm.Opcodes;

import static com.javatest.classfile.MyClassFileWriter.CONSTANT_Class_info;
import static com.javatest.classfile.MyClassFileWriter.CONSTANT_Fieldref_info;
import static com.javatest.classfile.MyClassFileWriter.CONSTANT_Methodref_info;
import static com.javatest.classfile.MyClassFileWriter.CONSTANT_NameAndType_info;
import static com.javatest.classfile.MyClassFileWriter.CONSTANT_String_info;
import static com.javatest.classfile.MyClassFileWriter.CONSTANT_Utf8_info;
import static com.javatest.classfile.MyClassFileWriter.OPCODE_ALOAD_0;
import static com.javatest.classfile.MyClassFileWriter.OPCODE_ALOAD_1;
import static com.javatest.classfile.MyClassFileWriter.OPCODE_ASTORE_1;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2023/11/29 20:23
 */
public class ClassFileTest {

    @Test
    public void testWriteClass() {
        MyClassFileWriter writer = new MyClassFileWriter();
        writer.writeU4(0xCAFE_BABEL)    // 魔数
                .writeU4(52);    // 主版本号，次版本号: jdk8

        /* ========== CONSTANT POOL ========== */

        // 常量个数
        writer.writeU2(31);

        // write class utf8 info
        // 1
        writeConstantUtf8Info(writer, "com/TestCode");
        // 2
        writeConstantUtf8Info(writer, "java/lang/Object");
        // 3
        writeConstantUtf8Info(writer, "java/lang/System");
        // 4
        writeConstantUtf8Info(writer, "java/io/PrintStream");

        // write field utf8 info
        // 5
        writeConstantUtf8Info(writer, "Ljava/io/PrintStream;");
        // 6
        writeConstantUtf8Info(writer, "out");

        // write method utf8 info
        // 7
        writeConstantUtf8Info(writer, "<init>");
        // 8
        writeConstantUtf8Info(writer, "()V");
        // 9
        writeConstantUtf8Info(writer, "print");
        // 10
        writeConstantUtf8Info(writer, "(Ljava/lang/String;)V");
        // 11
        writeConstantUtf8Info(writer, "main");
        // 12
        writeConstantUtf8Info(writer, "([Ljava/lang/String;)V");    // [代表数组

        // write literal utf8 info
        // 13
        writeConstantUtf8Info(writer, "Hello Class File");

        // Code属性用于存储方法体中的字节码指令
        // 14
        writeConstantUtf8Info(writer, "Code");

        // class info
        // 15
        writer.writeByte(CONSTANT_Class_info).writeU2(1);   // TestCode
        // 16
        writer.writeByte(CONSTANT_Class_info).writeU2(2);   // Object
        // 17
        writer.writeByte(CONSTANT_Class_info).writeU2(3);   // System
        // 18
        writer.writeByte(CONSTANT_Class_info).writeU2(4);   // PrintStream

        // name and type info
        // 19
        writer.writeByte(CONSTANT_NameAndType_info).writeU2(7).writeU2(8);  // <init>
        // 20
        writer.writeByte(CONSTANT_NameAndType_info).writeU2(6).writeU2(5);  // out
        // 21
        writer.writeByte(CONSTANT_NameAndType_info).writeU2(9).writeU2(10); // print

        // write methodref info
        // 22
        writer.writeByte(CONSTANT_Methodref_info).writeU2(16).writeU2(19);  // Object.init
        // 23
        writer.writeByte(CONSTANT_Methodref_info).writeU2(18).writeU2(21);  // PrintStream.print

        // write fieldref info
        // 24
        writer.writeByte(CONSTANT_Fieldref_info).writeU2(17).writeU2(20);   // System.out

        // write string info
        // 25
        writer.writeByte(CONSTANT_String_info).writeU2(13); // Hello Class File

        // 26
        writeConstantUtf8Info(writer, "LocalVariableTable");    // 用于局部变量表
        // 27
        writeConstantUtf8Info(writer, "args");
        // 28
        writeConstantUtf8Info(writer, "[Ljava/lang/String;");
        // 29
        writeConstantUtf8Info(writer, "this");
        // 30
        writeConstantUtf8Info(writer, "Lcom/TestCode;");

        /* ========== CLASS ========== */

        writer.writeU2(Opcodes.ACC_PUBLIC & Opcodes.ACC_SUPER)    // access_flags: public class
                .writeU2(15)    // this_class: TestCode
                .writeU2(16);    // super_class: Object

        /* ========== INTERFACE ========== */
        writer.writeU2(0);
        // nothing

        /* ========== FIELD ========== */
        writer.writeU2(0);
        // nothing

        /* ========== METHOD ========== */

        writer.writeU2(2);  // 方法个数： init、main

        writer.writeU2(Opcodes.ACC_PUBLIC)       // access_flags: public
                .writeU2(7)     // name_index: <init>
                .writeU2(8);     // descriptor_info: ()V
        MyClassFileWriter initCodeWriter = init();
        writer.writeU2(1);  // 属性的数量
        writer.writeU2(14)  // Code
                .writeU4(initCodeWriter.length())
                .writeAll(initCodeWriter);

        writer.writeU2(Opcodes.ACC_STATIC & Opcodes.ACC_PUBLIC)       // public static
                .writeU2(11)    // main
                .writeU2(12);    // ([LString;)V
        MyClassFileWriter mainCodeWriter = main();
        writer.writeU2(1);  // 属性的数量
        writer.writeU2(14)  // Code
                .writeU4(mainCodeWriter.length())
                .writeAll(mainCodeWriter);

        /* ========== CLASS ATTRIBUTE ========== */
        writer.writeU2(0);

        writer.print();
        writer.writeClassFile(ClassLoader.getSystemClassLoader().getResource("").getPath() + "/com/TestCode.class");
    }

    private static MyClassFileWriter main() {

        MyClassFileWriter codeWriter = new MyClassFileWriter();
        codeWriter.writeU2(2)  // 操作数栈深度
                .writeU2(2);     // 局部变量表大小

        /* ========== INSTRUCTIONS ATTRIBUTE ========== */
        MyClassFileWriter instructionsWriter = new MyClassFileWriter()
                .writeU1(Opcodes.GETSTATIC).writeU2(24)   // get System.out
                .writeU1(OPCODE_ASTORE_1)
                .writeU1(OPCODE_ALOAD_1)
                .writeU1(Opcodes.LDC).writeU1(25) // load "Hello Class File" to stack
                .writeU1(Opcodes.INVOKEVIRTUAL).writeU2(23)    // invoke print
                .writeU1(Opcodes.RETURN);

        codeWriter.writeU4(instructionsWriter.length())    // 长度
                .writeAll(instructionsWriter);

        /* ========== EXCEPTION TABLE ATTRIBUTE ========== */
        codeWriter.writeU2(0);  // 异常处理表大小

        /* ========== METHOD ATTRIBUTE ========== */
        codeWriter.writeU2(1);  // 属性表个数
        MyClassFileWriter localVariableTableWriter = new MyClassFileWriter()
                .writeU2(2)      // 局部变量表条目数
                // start_pc, length, var_name, var_descriptor, index
                .writeU2(0).writeU2(11).writeU2(27).writeU2(28).writeU2(0)
                .writeU2(4).writeU2(7).writeU2(6).writeU2(5).writeU2(1);
        codeWriter.writeU2(26)  // LocalVariableTable
                .writeU4(localVariableTableWriter.length())      // 长度
                .writeAll(localVariableTableWriter);
        return codeWriter;
    }

    private static MyClassFileWriter init() {

        MyClassFileWriter codeWriter = new MyClassFileWriter();
        codeWriter.writeU2(1)  // 操作数栈深度
                .writeU2(1);     // 局部变量表大小

        /* ========== INSTRUCTIONS ATTRIBUTE ========== */
        MyClassFileWriter instructionsWriter = new MyClassFileWriter()
                .writeU1(OPCODE_ALOAD_0)
                .writeU1(Opcodes.INVOKESPECIAL).writeU2(22)    // invoke init
                .writeU1(Opcodes.RETURN);

        codeWriter.writeU4(instructionsWriter.length())    // 长度
                .writeAll(instructionsWriter);

        /* ========== EXCEPTION TABLE ATTRIBUTE ========== */
        codeWriter.writeU2(0);  // 异常处理表大小

        /* ========== METHOD ATTRIBUTE ========== */
        codeWriter.writeU2(1);  // 属性表个数
        MyClassFileWriter localVariableTableWriter = new MyClassFileWriter()
                .writeU2(1)      // 局部变量表条目数
                // start_pc, length, var_name, var_descriptor, index
                .writeU2(0).writeU2(5).writeU2(29).writeU2(30).writeU2(0);
        codeWriter.writeU2(26)  // LocalVariableTable
                .writeU4(localVariableTableWriter.length())      // 长度
                .writeAll(localVariableTableWriter);
        return codeWriter;
    }

    private static void writeConstantUtf8Info(MyClassFileWriter writer, String utf8) {
        byte[] bytes = utf8.getBytes();
        writer.writeU1(CONSTANT_Utf8_info).writeU2(bytes.length).writeBytes(bytes);
    }

}
